/******************************************************************************
 * XWiki.JavaScriptExtension[0]: JavaScript Utilities for Exhibit
 * Copyright (C) 2009-2011, Niels Mayer. All Rights Reserved. http://nielsmayer.com
 ******************************************************************************/
/****************************************************************************
 * Called out of XWiki.JavaScriptExtension[2]:onLoadExhibit() via macro 
 * #exhibit_create(). This allows overriding existing Exhibit code by 
 * importing another Macro/Document after Macros.Exhibit (but prior to
 * final call to #exhibit_create()) which redefines the empty default
 * function, addExhibitInterceptorsAndOverides(), e.g.:
 * #includeMacros("Macros.Exhibit")##
 * #exhibit_xwiki_init()##
 * #includeMacros("ts.ExhibitOverridesJS") 
 * #exhibitoverrides_xwiki_init()
 ****************************************************************************/
function addExhibitInterceptorsAndOverides() {
 /*
  * NPM ENHANCEMENT for Exhibit.Functions[]  (
  * exhibit-trunk:trunk/src/webapp/api/scripts/data/functions.js ).
  * Added functions:
  *   -- zeroTime()
  *      :: used in conjunction with date-range() to convert a time value into a number.
  *   -- ISO8601-to-HMS(iso8601_string) 
  *      :: trims '<date>T' and 'Z' from iso8601 string
  *      :: e.g. <date>THH:mm:ss.SSSZ --> "HH:mm:ss.SSS"
  *   -- to-fixed(float_number, precision) 
  *      :: wrapper for .toFixed() method on javascript numbers.
  */
Exhibit.Functions["to-fixed"] = {
  f: function(args) {
    if ((args == null) || (args.size() != 2))
       return (new Exhibit.Expression._Collection([ ], "number"));
    else {
    var arg = args[0];
    switch (arg.valueType) {
    case 'number':
      var argVal = Exhibit.FunctionUtilities.exhibitFunargValue(arg);
      if (argVal != null) {
        var fixArg = args[1];
        switch (fixArg.valueType) {
        case 'number':
          var fixVal = Exhibit.FunctionUtilities.exhibitFunargValue(fixArg);
          if (fixVal != null)
            return (new Exhibit.Expression._Collection([ argVal.toFixed(fixVal) ], "number"));
          else
            return (new Exhibit.Expression._Collection([ ], "number"));
          break;
        default:
          return (new Exhibit.Expression._Collection([ ], "number"));
          break;
        }
      }
      else
        return (new Exhibit.Expression._Collection([ ], "number"));
      break;
    default:
      return (new Exhibit.Expression._Collection([ ], "number"));
      break;
    }
  }
  }
};
Exhibit.Functions["ISO8601-to-HMS"] = {
  f: function(args) {
    var arg = args[0];
    switch (arg.valueType) {
    case 'date':
      var t = Exhibit.FunctionUtilities.exhibitFunargValue(arg);
      if (t != null)
        return (new Exhibit.Expression._Collection([ t.substring(t.indexOf('T') + 1, t.lastIndexOf('Z')) ], "text"));
      else
        return (new Exhibit.Expression._Collection([ ], "text"));
      break;
    default:
      return (new Exhibit.Expression._Collection([ ], "text"));
      break;
    }
  }
};
Exhibit.Functions["zeroTime"] = {
    f: function(args) {
        return new Exhibit.Expression._Collection([ new Date(0) ], "date");
    }
};
 /*
  * NPM ENHANCEMENT: utility needed for functions ISO8601-to-HMS() and to-fixed()
  * Extends Exhibit.FunctionUtilities from trunk/src/webapp/api/scripts/data/functions.js
  */
Exhibit.FunctionUtilities.exhibitFunargValue = function (arg) {
  var argVal = null;
  arg.forEachValue(function(v) { argVal = v; })
  return (argVal);
};
 /*
  * NPM BUGFIX for Exhibit.ThumbnailView._itemContainerClass  (
  * exhibit-trunk:trunk/src/webapp/api/scripts/ui/views/thumbnail-view.js).
  * Got rid of special-case for exhibit-thumbnailView-itemContainer-IE
  * which seems designed for IE5/6, and breaks IE8.
  */
Exhibit.ThumbnailView._itemContainerClass = /* NPM: was: SimileAjax.Platform.browser.isIE ?
    "exhibit-thumbnailView-itemContainer-IE" : */
    "exhibit-thumbnailView-itemContainer";
 /*
  * NPM ENHANCEMENT/BUGFIX for Exhibit.UIContext._settingSpecs (
  * exhibit-trunk:trunk/src/webapp/api/scripts/ui/ui-context.js). Add
  * "bubbleOrientation" (text) representing values e.g. "top" "left"
  * "right" "bottom"). This change is needed to enable these features in
  * Exhibit.UI.showItemInPopup(),
  * Timeline.OriginalEventPainter.prototype._showBubble(),
  * Timeline.DetailedEventPainter.prototype._showBubble() and
  * Timeline.CompactEventPainter.prototype._showBubble().
  */
Exhibit.UIContext._settingSpecs = {
    "bubbleWidth":        { type: "int" },
    "bubbleHeight":       { type: "int" },
    "bubbleOrientation":  { type: "text"} //NPM ENHANCEMENT: ex:bubbleOrientation "top","left","right" or "bottom"
};
if (exhibit_views.indexOf("timeline") >= 0) { // Prevent errors if timeline not loaded/needed
/* 
 * NPM ENHANCEMENT for Exhibit.TimelineView._settingSpecs (
 * exhibit-trunk:trunk/src/webapp/api/extensions/time/scripts/timeline-view.js).
 * added top and bottom band 'multiple' feature of underlying timeline
 * widget.
 */
Exhibit.TimelineView._settingSpecs = {
    "topBandHeight":           { type: "int",        defaultValue: 75 },
    "topBandUnit":             { type: "enum",       choices: Exhibit.TimelineView._intervalChoices },
    "topBandPixelsPerUnit":    { type: "int",        defaultValue: 200 },
    "topBandMultiple":         { type: "int",        defaultValue: 1 }, //NPM ENHANCEMENT: ex:topBandMultiple
    "bottomBandHeight":        { type: "int",        defaultValue: 25 },
    "bottomBandUnit":          { type: "enum",       choices: Exhibit.TimelineView._intervalChoices },
    "bottomBandPixelsPerUnit": { type: "int",        defaultValue: 200 },
    "bottomBandMultiple":      { type: "int",        defaultValue: 1 }, //NPM ENHANCEMENT: ex:bottomBandMultiple
    "timelineHeight":          { type: "int",        defaultValue: 400 },
    "timelineConstructor":     { type: "function",   defaultValue: null },
    "colorCoder":              { type: "text",       defaultValue: null },
    "iconCoder":               { type: "text",       defaultValue: null },
    "selectCoordinator":       { type: "text",       defaultValue: null },
    "showHeader":              { type: "boolean",    defaultValue: true },
    "showSummary":             { type: "boolean",    defaultValue: true },
    "showFooter":              { type: "boolean",    defaultValue: true }
};
/* 
 * NPM ENHANCEMENT for
 * Exhibit.TimelineView.prototype._reconstructTimeline() (
 * exhibit-trunk/trunk/src/webapp/api/extensions/time/scripts/timeline-view.js).
 * Changes needed to enable "ex:bubbleOrientation" parameter to be passed
 * in to Timeline.DetailedEventPainter.prototype._showBubble() and
 * Timeline.CompactEventPainter.prototype._showBubble()
 */
Exhibit.TimelineView.prototype._reconstructTimeline = function(newEvents) {
    var settings = this._settings;
    
    if (this._timeline != null) {
        this._timeline.dispose();
    }
    
    if (newEvents) {
        this._eventSource.addMany(newEvents);
    }
    
    var timelineDiv = this._dom.plotContainer;
    if (settings.timelineConstructor != null) {
        this._timeline = settings.timelineConstructor(timelineDiv, this._eventSource);
    } else {
        timelineDiv.style.height = settings.timelineHeight + "px";
        timelineDiv.className = "exhibit-timelineView-timeline";

        var theme = Timeline.ClassicTheme.create();
        theme.event.bubble.width = this._uiContext.getSetting("bubbleWidth");
        theme.event.bubble.height = this._uiContext.getSetting("bubbleHeight");
        theme.event.bubble.orientation = this._uiContext.getSetting("bubbleOrientation"); //NPM ENHANCEMENT: ex:bubbleOrientation
        
        var topIntervalUnit, bottomIntervalUnit;
        if (settings.topBandUnit != null || settings.bottomBandUnit != null) {
            if (Exhibit.TimelineView._intervalLabelMap == null) {
                Exhibit.TimelineView._intervalLabelMap = {
                    "millisecond":      Timeline.DateTime.MILLISECOND,
                    "second":           Timeline.DateTime.SECOND,
                    "minute":           Timeline.DateTime.MINUTE,
                    "hour":             Timeline.DateTime.HOUR,
                    "day":              Timeline.DateTime.DAY,
                    "week":             Timeline.DateTime.WEEK,
                    "month":            Timeline.DateTime.MONTH,
                    "year":             Timeline.DateTime.YEAR,
                    "decade":           Timeline.DateTime.DECADE,
                    "century":          Timeline.DateTime.CENTURY,
                    "millennium":       Timeline.DateTime.MILLENNIUM
                };
            }
            
            if (settings.topBandUnit == null) {
                bottomIntervalUnit = Exhibit.TimelineView._intervalLabelMap[settings.bottomBandUnit];
                topIntervalUnit = bottomIntervalUnit - 1;
            } else if (settings.bottomBandUnit == null) {
                topIntervalUnit = Exhibit.TimelineView._intervalLabelMap[settings.topBandUnit];
                bottomIntervalUnit = topIntervalUnit + 1;
            } else {
                topIntervalUnit = Exhibit.TimelineView._intervalLabelMap[settings.topBandUnit];
                bottomIntervalUnit = Exhibit.TimelineView._intervalLabelMap[settings.bottomBandUnit];
            }
        } else { // figure this out dynamically
            var earliest = this._eventSource.getEarliestDate();
            var latest = this._eventSource.getLatestDate();
            
            var totalDuration = latest.getTime() - earliest.getTime();
            var totalEventCount = this._eventSource.getCount();
            if (totalDuration > 0 && totalEventCount > 1) {
                var totalDensity = totalEventCount / totalDuration;
                
                var topIntervalUnit = Timeline.DateTime.MILLENNIUM;
                while (topIntervalUnit > 0) {
                    var intervalDuration = Timeline.DateTime.gregorianUnitLengths[topIntervalUnit];
                    var eventsPerPixel = totalDensity * intervalDuration / settings.topBandPixelsPerUnit;
                    if (eventsPerPixel < 0.01) {
                        break;
                    }
                    topIntervalUnit--;
                }
            } else {
                topIntervalUnit = Timeline.DateTime.YEAR;
            }
            bottomIntervalUnit = topIntervalUnit + 1;
        }
        
        var bandInfos = [
            Timeline.createBandInfo({
                width:          settings.topBandHeight + "%", 
                intervalUnit:   topIntervalUnit, 
                intervalPixels: settings.topBandPixelsPerUnit,
                multiple:       settings.topBandMultiple,     //NPM ENHANCEMENT: ex:topBandMultiple
                eventSource:    this._eventSource,
                //date:           earliest,
                theme:          theme
            }),
            Timeline.createBandInfo({
                width:          settings.bottomBandHeight + "%", 
                intervalUnit:   bottomIntervalUnit, 
                intervalPixels: settings.bottomBandPixelsPerUnit,
                multiple:       settings.bottomBandMultiple, //NPM ENHANCEMENT: ex:bottomBandMultiple
                eventSource:    this._eventSource,
                //date:           earliest,
                overview:       true,
                theme:          theme
            })
        ];
        bandInfos[1].syncWith = 0;
        bandInfos[1].highlight = true;

        this._timeline = Timeline.create(timelineDiv, bandInfos, Timeline.HORIZONTAL);
    }
    
    var self = this;
    var listener = function(eventID) {
        if (self._selectListener != null) {
            self._selectListener.fire({ itemIDs: [ eventID ] });
        }
    }
    for (var i = 0; i < this._timeline.getBandCount(); i++) {
        this._timeline.getBand(i).getEventPainter().addOnSelectListener(listener);
    }
};
 /*
  * NPM ENHANCEMENT/BUGFIX for
  * Timeline.OriginalEventPainter.prototype._showBubble()
  * (timeline-2.3.1:2.3.1/src/webapp/api/scripts/original-painter.js). The
  * original code always puts bubble "above" due to ommission of the
  * orientation parameter to underlying call
  * SimileAjax.Graphics.createBubbleForContentAndPoint(). This enhancement
  * allows ex:bubbleOrientation parameter to change the orientation to
  * "top" "left" "right" or "bottom". Exhibit parameter ex:bubbleHeight was
  * was ignored due to a bug of referencing nonexistent
  * theme.event.bubble.maxHeight correcting to theme.event.bubble.height
  * allows setting bubble height through existing mechanisms
  * (Exhibit.UIContext._settingSpecs). Note: the two other similar
  * ._showBubble() methods are not invoked in Exhibit's default use of
  * timeline. However, for consistency, the same change applied to all
  * three methods.
  */
Timeline.OriginalEventPainter.prototype._showBubble = function(x, y, evt) {
    var div = document.createElement("div");
    var themeBubble = this._params.theme.event.bubble;
    evt.fillInfoBubble(div, this._params.theme, this._band.getLabeller());
    
    SimileAjax.WindowManager.cancelPopups();
    SimileAjax.Graphics.createBubbleForContentAndPoint(div, x, y, 
						       themeBubble.width, themeBubble.orientation, themeBubble.height); //NPM BUGFIX/ENHANCEMENT: was "themeBubble.width, null, themeBubble.maxHeight);"
};
 /*
  * NPM ENHANCEMENT/BUGFIX for
  * Timeline.DetailedEventPainter.prototype._showBubble()
  * (timeline-2.3.1:2.3.1/src/webapp/api/scripts/detailed-painter.js). The
  * original code always puts bubble "above" due to ommission of the
  * orientation parameter to underlying call
  * SimileAjax.Graphics.createBubbleForContentAndPoint(). This enhancement
  * allows ex:bubbleOrientation parameter to change the orientation to
  * "top" "left" "right" or "bottom". Exhibit parameter ex:bubbleHeight was
  * was ignored due to a bug of referencing nonexistent
  * theme.event.bubble.maxHeight correcting to theme.event.bubble.height
  * allows setting bubble height through existing mechanisms
  * (Exhibit.UIContext._settingSpecs). Note:
  * Timeline.OriginalEventPainter.prototype._showBubble() above is the one
  * used by default in exhibit, this one isn't. However, the same
  * bugfix/enhancement was made.
  */
Timeline.DetailedEventPainter.prototype._showBubble = function(x, y, evt) {
    var div = document.createElement("div");
    var themeBubble = this._params.theme.event.bubble;
    evt.fillInfoBubble(div, this._params.theme, this._band.getLabeller());
    
    SimileAjax.WindowManager.cancelPopups();
    SimileAjax.Graphics.createBubbleForContentAndPoint(div, x, y, 
						       themeBubble.width, themeBubble.orientation, themeBubble.height); //NPM BUGFIX/ENHANCEMENT: was "themeBubble.width, null, themeBubble.maxHeight);"
};
 /*
  * NPM BUGFIX/ENHANCEMENT for
  * Timeline.CompactEventPainter.prototype._showBubble()
  * (timeline-2.3.1:2.3.1/src/webapp/api/scripts/compact-painter.js).  The
  * original code always puts bubble "above" due to ommission of the
  * orientation parameter to underlying call
  * SimileAjax.Graphics.createBubbleForContentAndPoint(). This enhancement
  * allows ex:bubbleOrientation parameter to change the orientation to
  * "top" "left" "right" or "bottom". Exhibit parameter ex:bubbleHeight was
  * was ignored due to a bug of referencing nonexistent
  * theme.event.bubble.maxHeight correcting to theme.event.bubble.height
  * allows setting bubble height through existing mechanisms
  * (Exhibit.UIContext._settingSpecs). Note:
  * Timeline.OriginalEventPainter.prototype._showBubble() above is the one
  * used by default in exhibit, this one isn't. However, the same
  * bugfix/enhancement was made.
  */
Timeline.CompactEventPainter.prototype._showBubble = function(x, y, evts) {
    var div = document.createElement("div");
    var themeBubble = this._params.theme.event.bubble;  //NPM added

    evts = ("fillInfoBubble" in evts) ? [evts] : evts;
    for (var i = 0; i < evts.length; i++) {
        var div2 = document.createElement("div");
        div.appendChild(div2);
        
        evts[i].fillInfoBubble(div2, this._params.theme, this._band.getLabeller());
    }
    
    SimileAjax.WindowManager.cancelPopups();
    SimileAjax.Graphics.createBubbleForContentAndPoint(div, x, y, //NPM: was: 'this._params.theme.event.bubble.width);' missing some params
        themeBubble.width,       //param contentWidth
        themeBubble.orientation, //NPM ENHANCEMENT: param orientation: a string ("top", "bottom", "left", or "right")
        themeBubble.height       //NPM BUGFIX: param maxHeight: an int
        );
};
}// end: "if (exhibit_views.indexOf("timeline") >= 0)"
 /*
  * NPM BUGFIX/ENHANCEMENT for Exhibit.UI.showItemInPopup()
  * (exhibit-trunk:trunk/src/webapp/api/scripts/ui/ui.js) The original code
  * always puts bubble "above" due to ommission of the orientation
  * parameter to underlying call
  * SimileAjax.Graphics.createBubbleForContentAndPoint(). This enhancement
  * allows ex:bubbleOrientation parameter to change the orientation to
  * "top" "left" "right" or "bottom". Exhibit parameter ex:bubbleHeight was
  * was also omitted, causing a default setting to be used, this fix
  * enables existing mechanisms (Exhibit.UIContext._settingSpecs) to
  * properly set bubble height.  Similar to changes in
  * Timeline.CompactEventPainter.prototype._showBubble(),
  * Timeline.DetailedEventPainter.prototype._showBubble() and
  * Timeline.OriginalEventPainter.prototype._showBubble(), this change
  * enables bubble height/width/orientation settings in tabular view just
  * like in timeline by adding parameters to
  * <div ex:role="view" ex:bubbleWidth="400" ex:bubbleHeight="200"
  *      ex:bubbleOrientation="bottom" ...>
  */
Exhibit.UI.showItemInPopup = function(itemID, elmt, uiContext, opts) {
    SimileAjax.WindowManager.popAllLayers();
    
    opts = opts || {};
    opts.coords = opts.coords || Exhibit.UI.calculatePopupPosition(elmt);
    
    var itemLensDiv = document.createElement("div");

    var lensOpts = {
        inPopup: true,
        coords: opts.coords
    };

    if (opts.lensType == 'normal') {
        lensOpts.lensTemplate = uiContext.getLensRegistry().getNormalLens(itemID, uiContext);
    } else if (opts.lensType == 'edit') {
        lensOpts.lensTemplate = uiContext.getLensRegistry().getEditLens(itemID, uiContext);
    } else if (opts.lensType) {
        SimileAjax.Debug.warn('Unknown Exhibit.UI.showItemInPopup opts.lensType: ' + opts.lensType);
    }

    uiContext.getLensRegistry().createLens(itemID, itemLensDiv, uiContext, lensOpts);
    SimileAjax.Graphics.createBubbleForContentAndPoint(
        itemLensDiv, 
        opts.coords.x,
        opts.coords.y, 
        uiContext.getSetting("bubbleWidth"),       //param contentWidth
        uiContext.getSetting("bubbleOrientation"), //NPM ENHANCEMENT: param orientation: a string ("top", "bottom", "left", or "right")
        uiContext.getSetting("bubbleHeight")       //NPM BUGFIX: param maxHeight: an int
    );
};
if (exhibit_views.indexOf("timeplot") >= 0) // Prevent errors if timeplot not loaded/needed
  setTimeout(function() { 
 /* 
  * TODO (not neccessary?): Timeplot "Workaround for stale mouseevents"
  * http://code.google.com/p/simile-widgets/issues/detail?id=311  
  */
 /*
  * NPM BUGFIX Timeplot.DefaultTimeGeometry.prototype._calculateGrid originally from
  * http://code.google.com/p/simile-widgets/source/browse/timeplot/trunk/src/webapp/api/scripts/geometry.js
  * This particular change comes from a patch supplied on Timeplot: 
  * http://code.google.com/p/simile-widgets/issues/detail?id=312
  * "Changed global 'unit' variable to be of local scope" 
  * (which also corrects a programming issue)
  * -------------
  * This function calculates the grid spacing that it will be used 
  * by this geometry to draw the grid in order to reduce clutter.
  */
 Timeplot.DefaultTimeGeometry.prototype._calculateGrid = function() {
        var grid = [];
        
        var time = SimileAjax.DateTime;
        var u = this._unit;
        var p = this._period;
        
        if (p == 0) return grid;
        
        var unit = time.MILLENNIUM;; /*was:(missing)*/
        // find the time units nearest to the time period
/*was:
        if (p > time.gregorianUnitLengths[time.MILLENNIUM]) {
           unit = time.MILLENNIUM;
        } else {
*/
        if (p <= time.gregorianUnitLengths[time.MILLENNIUM]) {
            for ( /*was:var unit = time.MILLENNIUM*/ ; unit > 0; unit--) {
                if (time.gregorianUnitLengths[unit-1] <= p && p < time.gregorianUnitLengths[unit]) {
                    unit--;
                    break;
                }
            }
        }

        var t = u.cloneValue(this._earliestDate);

        do {
            time.roundDownToInterval(t, unit, this._timeZone, 1, 0);
            var x = this.toScreen(u.toNumber(t));
         /* 
          * NPM: changed switch() cases below to work in UTC correctly when using timeplot's
          * ex:timeGeometry="timeplotGeometer" == new Timeplot.DefaultTimeGeometry({ timeZone: 0 })
          * Prior code incorrectly used t.toLocaleTimeString() & t.toLocaleDateString() which caused
          * data to be printed in local timezone rather than the data's UTC/GMT timezone. Also,
          * original code printed labels that overlapped due to varying length and long sizes.
          */
            switch (unit) {
                case time.SECOND:
                  var s = t.getUTCSeconds();
                  var l = ((s < 10) ? "0" : "") + s;
                  break;
                case time.MINUTE:
                  var m = t.getUTCMinutes();
                  var l = ((m < 10) ? "0" : "") + m;
                  break;
                case time.HOUR:
                  var h = t.getUTCHours();
                  var l = ((h < 10) ? "0" : "") + h + ":00";
                  break;
                case time.DAY:
                case time.WEEK:
                case time.MONTH:
                  var l = t.toUTCString();
                  break;  
                case time.YEAR:
                case time.DECADE:
                case time.CENTURY:
                case time.MILLENNIUM:
                  var l = t.getUTCFullYear();
                  break;
            }
            if (x > 0) { 
                grid.push({ x: x, label: l });
            }
            time.incrementByInterval(t, unit, this._timeZone);
        } while (t.getTime() < this._latestDate.getTime());
        
        return grid;
  };
  /**
   * NPM BUGFIX Timeplot.Plot.prototype.initialize() originally from
   * http://code.google.com/p/simile-widgets/source/browse/timeplot/trunk/src/webapp/api/scripts/plot.js
   * --------------
   * Initialize the plot layer
   */
  Timeplot.Plot.prototype.initialize = function() {
        if ((this._dataSource != null) && (this._dataSource.getValue != null)) { /*was: if (this._dataSource && this._dataSource.getValue) */
            this._timeFlag = this._timeplot.putDiv("timeflag","timeplot-timeflag");
            this._valueFlag = this._timeplot.putDiv(this._id + "valueflag","timeplot-valueflag");
            this._valueFlagLineLeft = this._timeplot.putDiv(this._id + "valueflagLineLeft","timeplot-valueflag-line");
            this._valueFlagLineRight = this._timeplot.putDiv(this._id + "valueflagLineRight","timeplot-valueflag-line");
            if (!this._valueFlagLineLeft.firstChild) {
                this._valueFlagLineLeft.appendChild(SimileAjax.Graphics.createTranslucentImage(Timeplot.urlPrefix + "images/line_left.png"));
                this._valueFlagLineRight.appendChild(SimileAjax.Graphics.createTranslucentImage(Timeplot.urlPrefix + "images/line_right.png"));
            }
            this._valueFlagPole = this._timeplot.putDiv(this._id + "valuepole","timeplot-valueflag-pole");

            var opacity = this._plotInfo.valuesOpacity;
            
            SimileAjax.Graphics.setOpacity(this._timeFlag, opacity);
            SimileAjax.Graphics.setOpacity(this._valueFlag, opacity);
            SimileAjax.Graphics.setOpacity(this._valueFlagLineLeft, opacity);
            SimileAjax.Graphics.setOpacity(this._valueFlagLineRight, opacity);
            SimileAjax.Graphics.setOpacity(this._valueFlagPole, opacity);

            var plot = this;
            
            var mouseOverHandler = function(elmt, evt, target) {
                if (plot._plotInfo.showValues) { 
                        plot._valueFlag.style.display = "block";
                        mouseMoveHandler(elmt, evt, target);
                    }
            }
        
            var day = 24 * 60 * 60 * 1000;
            var month = 30 * day;
            
            var mouseMoveHandler = function(elmt, evt, target) {
                if (typeof SimileAjax != "undefined" && plot._plotInfo.showValues) {
                    var c = plot._canvas;
                    var x = Math.round(SimileAjax.DOM.getEventRelativeCoordinates(evt,plot._canvas).x);
                    if (x > c.width) x = c.width;
                    if (isNaN(x) || x < 0) x = 0;
                    var t = plot._timeGeometry.fromScreen(x);
                    if (t == 0) { // something is wrong
                        plot._valueFlag.style.display = "none";
                        return;
                    }
                    
                    var validTime = plot._dataSource.getClosestValidTime(t);
                    x = plot._timeGeometry.toScreen(validTime);
                    var v = plot._dataSource.getValue(validTime);
                    if (plot._plotInfo.roundValues) v = Math.round(v);
                    plot._valueFlag.innerHTML = new String(v);
                    var d = new Date(validTime);
                    var p = plot._timeGeometry.getPeriod(); 
                    if (p < day) {
                        plot._timeFlag.innerHTML = /*d.getUTCHours()+':'+*/ d.getUTCMinutes()+':'+d.getUTCSeconds()+'.'+d.getUTCMilliseconds() /*was:d.toLocaleTimeString()*/ ;
                    } else if (p > month) {
                        plot._timeFlag.innerHTML = d.toLocaleDateString(); /*NPM: leave in local TZ since returns date w/o time; there's no equiv UTC fn */
                    } else {
                        plot._timeFlag.innerHTML = d.toUTCString() /*was:d.toLocaleString()*/ ;
                    }

                    var tw = plot._timeFlag.clientWidth;
                    var th = plot._timeFlag.clientHeight;
                    var tdw = Math.round(tw / 2);
                    var vw = plot._valueFlag.clientWidth;
                    var vh = plot._valueFlag.clientHeight;
                    var y = plot._valueGeometry.toScreen(v);

                    if (x + tdw > c.width) {
                        var tx = c.width - tdw;
                    } else if (x - tdw < 0) {
                        var tx = tdw;
                    } else {
                        var tx = x;
                    }

                    if (plot._timeGeometry._timeValuePosition == "top") {
                        plot._timeplot.placeDiv(plot._valueFlagPole, {
                            left: x,
                            top: th - 5,
                            height: c.height - y - th + 6,
                            display: "block"
                        });
                        plot._timeplot.placeDiv(plot._timeFlag,{
                            left: tx - tdw,
                            top: -6,
                            display: "block"
                        });
                    } else {
                        plot._timeplot.placeDiv(plot._valueFlagPole, {
                            left: x,
                            bottom: th - 5,
                            height: y - th + 6,
                            display: "block"
                        });
                        plot._timeplot.placeDiv(plot._timeFlag,{
                            left: tx - tdw,
                            bottom: -6,
                            display: "block"
                        });
                    }

                    if (x + vw + 14 > c.width && y + vh + 4 > c.height) {
                        plot._valueFlagLineLeft.style.display = "none";
                        plot._timeplot.placeDiv(plot._valueFlagLineRight,{
                            left: x - 14,
                            bottom: y - 14,
                            display: "block"
                        });
                        plot._timeplot.placeDiv(plot._valueFlag,{
                            left: x - vw - 13,
                            bottom: y - vh - 13,
                            display: "block"
                        });
                    } else if (x + vw + 14 > c.width && y + vh + 4 < c.height) {
                        plot._valueFlagLineRight.style.display = "none";
                        plot._timeplot.placeDiv(plot._valueFlagLineLeft,{
                            left: x - 14,
                            bottom: y,
                            display: "block"
                        });
                        plot._timeplot.placeDiv(plot._valueFlag,{
                            left: x - vw - 13,
                            bottom: y + 13,
                            display: "block"
                        });
                    } else if (x + vw + 14 < c.width && y + vh + 4 > c.height) {
                        plot._valueFlagLineRight.style.display = "none";
                        plot._timeplot.placeDiv(plot._valueFlagLineLeft,{
                            left: x,
                            bottom: y - 13,
                            display: "block"
                        });
                        plot._timeplot.placeDiv(plot._valueFlag,{
                            left: x + 13,
                            bottom: y - 13,
                            display: "block"
                        });
                    } else {
                        plot._valueFlagLineLeft.style.display = "none";
                        plot._timeplot.placeDiv(plot._valueFlagLineRight,{
                            left: x,
                            bottom: y,
                            display: "block"
                        });
                        plot._timeplot.placeDiv(plot._valueFlag,{
                            left: x + 13,
                            bottom: y + 13,
                            display: "block"
                        });
                    }
                }
            }

            var timeplotElement = this._timeplot.getElement();
            SimileAjax.DOM.registerEvent(timeplotElement, "mouseover", mouseOverHandler);
            SimileAjax.DOM.registerEvent(timeplotElement, "mousemove", mouseMoveHandler);
        }
  };
  /** NPM BUGFIX Timeplot.Plot.prototype.paint() originally from
   * http://code.google.com/p/simile-widgets/source/browse/timeplot/trunk/src/webapp/api/scripts/plot.js
   * ----
   * Paint this plot layer
   */
  Timeplot.Plot.prototype.paint = function() {
        var ctx = this._canvas.getContext('2d');

        ctx.lineWidth = this._plotInfo.lineWidth;
        ctx.lineJoin = 'miter';

        if (this._dataSource != null) {	/*was: if (this._dataSource) */
	  if (this._plotInfo.fillColor != null) { /*was: if (this._plotInfo.fillColor) */
	    if (this._plotInfo.fillGradient != null) { /*was: if (this._plotInfo.fillGradient) */
                    var gradient = ctx.createLinearGradient(0,this._canvas.height,0,0);
                    gradient.addColorStop(0,this._plotInfo.fillColor.toString());
                    gradient.addColorStop(0.5,this._plotInfo.fillColor.toString());
                    gradient.addColorStop(1, 'rgba(255,255,255,0)');

                    ctx.fillStyle = gradient;
                } else {
                    ctx.fillStyle = this._plotInfo.fillColor.toString();
                }

                ctx.beginPath();
                ctx.moveTo(0,0);
                this._plot(function(x,y) {
                    ctx.lineTo(x,y);
                });
                if (this._plotInfo.fillFrom == Number.NEGATIVE_INFINITY) {
                    ctx.lineTo(this._canvas.width, 0);
                } else if (this._plotInfo.fillFrom == Number.POSITIVE_INFINITY) {
                    ctx.lineTo(this._canvas.width, this._canvas.height);
                    ctx.lineTo(0, this._canvas.height);
                } else {
                    ctx.lineTo(this._canvas.width, this._valueGeometry.toScreen(this._plotInfo.fillFrom));
                    ctx.lineTo(0, this._valueGeometry.toScreen(this._plotInfo.fillFrom));
                }
                ctx.fill();
            }
                    
            if (this._plotInfo.lineColor != null) { /*was: if (this._plotInfo.lineColor) */
                ctx.strokeStyle = this._plotInfo.lineColor.toString();
                ctx.beginPath();
                var first = true;
                this._plot(function(x,y) {
                        if (first) {
                             first = false;
                             ctx.moveTo(x,y);
                        }
                    ctx.lineTo(x,y);
                });
                ctx.stroke();
            }

            if (this._plotInfo.dotColor != null) { /*was: if (this._plotInfo.dotColor) */
                ctx.fillStyle = this._plotInfo.dotColor.toString();
                var r = this._plotInfo.dotRadius;
                this._plot(function(x,y) {
                    ctx.beginPath();
                    ctx.arc(x,y,r,0,2*Math.PI,true);
                    ctx.fill();
                });
            }
        }

        if (this._eventSource != null) { /*was: if (this._eventSource) */
            var gradient = ctx.createLinearGradient(0,0,0,this._canvas.height);
            gradient.addColorStop(1, 'rgba(255,255,255,0)');

            ctx.strokeStyle = gradient;
            ctx.fillStyle = gradient; 
            ctx.lineWidth = this._plotInfo.eventLineWidth;
            ctx.lineJoin = 'miter';
            
            var i = this._eventSource.getAllEventIterator();
            while (i.hasNext()) {
                var event = i.next();
                var color = event.getColor();
                color = (color) ? new Timeplot.Color(color) : this._plotInfo.lineColor;
                var eventStart = event.getStart().getTime();
                var eventEnd = event.getEnd().getTime();
                if (eventStart == eventEnd) {
                    var c = color.toString();
                    gradient.addColorStop(0, c);
                    var start = this._timeGeometry.toScreen(eventStart);
                    start = Math.floor(start) + 0.5; // center it between two pixels (makes the rendering nicer)
                    var end = start;
                    ctx.beginPath();
                    ctx.moveTo(start,0);
                    ctx.lineTo(start,this._canvas.height);
                    ctx.stroke();
                    var x = start - 4;
                    var w = 7;
                } else {
                    var c = color.toString(0.5);
                    gradient.addColorStop(0, c);
                    var start = this._timeGeometry.toScreen(eventStart);
                    start = Math.floor(start) + 0.5; // center it between two pixels (makes the rendering nicer)
                    var end = this._timeGeometry.toScreen(eventEnd);
                    end = Math.floor(end) + 0.5; // center it between two pixels (makes the rendering nicer)
                    ctx.fillRect(start,0,end - start, this._canvas.height);
                    var x = start;
                    var w = end - start - 1;
                }

                var div = this._timeplot.putDiv(event.getID(),"timeplot-event-box",{
                    left: Math.round(x),
                    width: Math.round(w),
                    top: 0,
                    height: this._canvas.height - 1
                });

                var plot = this;
                var clickHandler = function(event) { 
                    return function(elmt, evt, target) { 
                        var doc = plot._timeplot.getDocument();
                        plot._closeBubble();
                        var coords = SimileAjax.DOM.getEventPageCoordinates(evt);
                        var elmtCoords = SimileAjax.DOM.getPageCoordinates(elmt);
                        plot._bubble = SimileAjax.Graphics.createBubbleForPoint(coords.x, elmtCoords.top + plot._canvas.height, plot._plotInfo.bubbleWidth, plot._plotInfo.bubbleHeight, "bottom");
                        event.fillInfoBubble(plot._bubble.content, plot._theme, plot._timeGeometry.getLabeler());
                    }
                };
                var mouseOverHandler = function(elmt, evt, target) {
                    elmt.oldClass = elmt.className;
                    elmt.className = elmt.className + " timeplot-event-box-highlight";
                };
                var mouseOutHandler = function(elmt, evt, target) {
                    elmt.className = elmt.oldClass;
                    elmt.oldClass = null;
                }
                
                if (!div.instrumented) {
                    SimileAjax.DOM.registerEvent(div, "click"    , clickHandler(event));
                    SimileAjax.DOM.registerEvent(div, "mouseover", mouseOverHandler);
                    SimileAjax.DOM.registerEvent(div, "mouseout" , mouseOutHandler);
                    div.instrumented = true;
                }
            }
        }
    };
 }, 1000); //end of "addTimeout(function {" above
  /**
   * This allows setting of data.items[] from document objects, after exhibit loads, but
   * prior to display. This allows dynamic data to be computed from document objects
   * prior to rendering in Exhibit. Empty Exhibit data results in no display, so we want
   * at least one event -- this one spans out the entire length of the media.
   */
  setApplicationDataToExhibitDataItems(); // callback defined in ts.SpodderClassSheet.
/************ end:addExhibitInterceptorsAndOverides() ****************/
}
